﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace EBA.Linq
{
    public static class LambdaExpressionSerializer
    {
        public static string Serialize<T>(this Expression<Func<T, bool>> expression)
        {
            if (expression == null)
            {
                return "i => ";
            }
            else
            {
                var command = expression.Parameters[0].Name + " => ";
                return command + Evaluate(expression.Body);
            }
        }

        public static Expression<Func<T, bool>> ToWhere<T>(this string expression)
        {
            if (string.IsNullOrWhiteSpace(expression) || expression == "i => ")
            {
                return null;
            }
            else
            {

                var name = expression[0].ToString();

                ParameterExpression parameter = Expression.Parameter(typeof(T), name);
                Expression body = MakeExpression<T>(expression.Substring(5), parameter);

                Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, parameter);

                return lambda;
            }
        }


        #region Deserialize Helper
        static BinaryExpression MakeBinary<T>(ExpressionType op, string expression, ParameterExpression parameter, OperatorPart operatorpart, int leftPos)
        {
            var expr = expression.Trim();
            var left = "";
            var right = "";
            OperatorPart opPos = operatorpart;

            left = expr.Substring(0, opPos.StartPos).Trim();
            right = expr.Substring(leftPos + 1, expr.Length - leftPos - 2);
            var entityType = typeof(T);
            int n = 0;
            int j = 0;
            string[] leftExpStr = new string[5];
            while (left.LastIndexOf(".") != -1)
            {
                n = left.LastIndexOf(".");
                leftExpStr[j] = left.Substring(n + 1);
                left = left.Substring(0, left.Length - leftExpStr[j].Length - 1);
                j++;
            }

            var pi = entityType.GetProperty(leftExpStr[j - 1]);
            var leftExpr = LambdaExpression.Property(parameter, pi);
            int t = 2;
            while (j - t != -1)
            {
                pi = pi.PropertyType.GetProperty(leftExpStr[j - t]);
                leftExpr = LambdaExpression.Property(leftExpr, pi);
                t++;
            }

            object constant;


            if (right == "null")
            {
                constant = null;
            }
            else
            {
                if (pi.PropertyType.IsEnum)
                {
                    constant = Enum.Parse(pi.PropertyType, right);
                }
                else if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    if (right == "null")
                    {
                        constant = null;
                    }
                    else
                    {
                        constant = Convert.ChangeType(right, pi.PropertyType.GetGenericArguments()[0]);
                    }
                }
                else
                {
                    constant = Convert.ChangeType(right, pi.PropertyType);
                }
            }
            var rightExpr = LambdaExpression.Constant(constant, pi.PropertyType);

            return LambdaExpression.MakeBinary(op, leftExpr, rightExpr);
        }

        static Expression MakeExpression<T>(string expression, ParameterExpression parameter)
        {
            var expr = expression.Trim();

            var left = "";
            ExpressionType op = ExpressionType.Default;
            var right = "";
            var leftPos = -1;
            OperatorPart opPos = null;
            for (var i = expr.Length - 1; i > -1; i--)
            {
                var current = expr[i];
                switch (current)
                {
                    case ')':
                        leftPos = GetLeftBracket(expr, i - 1);
                        right = expr.Substring(leftPos + 1, expr.Length - leftPos - 2);
                        opPos = GetOperator(expr, leftPos - 1);

                        if (opPos != null)
                        {
                            var opName = opPos.Name;
                            switch (opName)
                            {
                                case "&&":
                                    op = ExpressionType.AndAlso;
                                    break;
                                case "||":
                                    op = ExpressionType.OrElse;
                                    break;
                                default:
                                    throw new NotSupportedException();
                            }

                            left = expr.Substring(0, opPos.StartPos);

                            return LambdaExpression.MakeBinary(op, MakeExpression<T>(left, parameter), MakeExpression<T>(right, parameter));
                        }
                        else
                        {
                            return MakeExpression<T>(right, parameter);
                        }
                    case '\"':
                        leftPos = GetLeftQuotePos(expr, i - 1);
                        right = expr.Substring(leftPos + 1, expr.Length - leftPos - 2);
                        opPos = GetOperator(expr, leftPos - 1);

                        if (opPos != null)
                        {
                            var opName = opPos.Name;
                            if (opName == "like" || opName == "in")
                            {
                                return MakeLike<T>(expr, parameter, opPos);
                            }
                            else
                            {
                                switch (opName)
                                {
                                    case "==":
                                        op = ExpressionType.Equal;
                                        break;
                                    case "!=":
                                        op = ExpressionType.NotEqual;
                                        break;
                                    case ">":
                                        op = ExpressionType.GreaterThan;
                                        break;
                                    case ">=":
                                        op = ExpressionType.GreaterThanOrEqual;
                                        break;
                                    case "<":
                                        op = ExpressionType.LessThan;
                                        break;
                                    case "<=":
                                        op = ExpressionType.LessThanOrEqual;
                                        break;
                                    default:
                                        throw new NotSupportedException();
                                }
                            }
                        }
                        return MakeBinary<T>(op, expression, parameter, opPos, leftPos);
                    default:
                        throw new NotSupportedException();
                }
            }
            return MakeBinary<T>(op, expression, parameter, opPos, leftPos);
        }

        static Expression MakeLike<T>(string expression, ParameterExpression parameter, OperatorPart operatorpart)
        {
            var left = expression.Substring(2, operatorpart.StartPos - expression.IndexOf(".") - 2);
            var methodStr = expression.Substring(operatorpart.StartPos + operatorpart.Length + 2, expression.LastIndexOf("\"") - expression.IndexOf("\"") - 1);
            var method = "";
            string obj = "";
            if (methodStr.IndexOf("%") == 0)
            {
                if (methodStr.LastIndexOf("%") != 0)
                {
                    method = "Contains";
                    obj = methodStr.Substring(1, methodStr.LastIndexOf("%") - methodStr.IndexOf("%") - 1);
                }
                else
                {
                    method = "EndsWith";
                    obj = methodStr.Substring(1);

                }
            }
            else
            {
                method = "StartsWith";
                obj = methodStr.Substring(0, methodStr.LastIndexOf("%"));
            }

            if (operatorpart.Name != "in")
            {
                return Expression.Call(Expression.Property(parameter, typeof(T).GetProperty(left)),
                    typeof(T).GetProperty(left).PropertyType.GetMethod(method, new Type[] { typeof(string) }), Expression.Constant(obj));
            }
            else
            {
                var list = new List<object>();
                string[] str = obj.Split(',');
                foreach (var i in str)
                {
                    list.Add(i);
                }

                ParameterExpression entityParameter = Expression.Parameter(typeof(T), "i");
                MemberExpression memberExpression = Expression.Property(entityParameter, "Name");
                ConstantExpression foreignKeysParameter = Expression.Constant(list, typeof(List<object>));
                //Expression convertExpression = Expression.Convert(memberExpression, typeof(object));

                return Expression.Call(foreignKeysParameter
                                      , "Contains", new Type[] { }, memberExpression);
            }








        }

        private static int GetLeftBracket(string expr, int pos)
        {
            var start = pos;

            var r = 0;

            while (start > -1)
            {
                var current = expr[start];

                switch (current)
                {

                    case '"':
                        start = GetLeftQuotePos(expr, start - 1);
                        break;
                    case ')':
                        r++;
                        break;
                    case '(':
                        if (r == 0)
                        {
                            return start;
                        }
                        else
                        {
                            r--;
                            break;
                        }
                    default:
                        break;
                }

                start--;

            }

            return start;
        }

        private static int GetLeftQuotePos(string expr, int pos)
        {
            var start = pos;

            while (start > -1)
            {
                var current = expr[start];

                switch (current)
                {
                    case '"':
                        var pre = expr[start - 1];
                        if (pre != '\\')
                        {
                            return start;
                        }
                        else
                        {
                            break;
                        }

                    default:
                        break; ;
                }

                start--;
            }

            return start;
        }

        static string[] Operators = new string[] { "||", "&&", "==", "!=", ">", "<", "<=", ">=", "like", "in" };

        private static OperatorPart GetOperator(string expr, int pos)
        {
            var start = pos;
            while (start > -1)
            {
                var op = expr.Substring(start, 1);

                if (Operators.Contains(op))
                {
                    return new OperatorPart { Length = 1, Name = op, StartPos = start };
                }
                else
                {

                    op = expr.Substring(start, 2);
                    if (Operators.Contains(expr.Substring(start, 2)))
                    {
                        return new OperatorPart { Length = 2, Name = op, StartPos = start };

                    }
                    else if (Operators.Contains(expr.Substring(start, 4)))
                    {
                        return new OperatorPart { Length = 4, Name = expr.Substring(start, 4), StartPos = start };
                    }
                }

                start--;

            }

            return null;
        }

        class OperatorPart
        {
            public int StartPos
            {
                get;
                set;
            }
            public int Length
            {
                get;
                set;
            }

            public string Name
            {
                get;
                set;
            }
        }

        #endregion

        #region Serialize Helper
        static string Evaluate(Expression expression)
        {
            object returnValue;

            if (expression is BinaryExpression)
            {
                return Evaluate((BinaryExpression)expression);
            }
            else
            {

                switch (expression.NodeType)
                {
                    case ExpressionType.Constant:
                        returnValue = ((ConstantExpression)expression).Value;
                        break;
                    case ExpressionType.MemberAccess:
                        var member = (MemberExpression)expression;
                        string _str = member.ToString();
                        //if (member.Expression != null && member.Expression.NodeType == ExpressionType.Parameter)
                        if (_str.IndexOf("i") == 0)
                        {
                            return member.ToString();
                        }
                        else
                        {
                            var objectMember = Expression.Convert(member, typeof(object));

                            var getterLambda = Expression.Lambda<Func<object>>(objectMember);

                            var getter = getterLambda.Compile();

                            returnValue = getter();

                            break;
                        }

                    case ExpressionType.Lambda:
                        if (expression is BinaryExpression)
                        {
                            return Evaluate((BinaryExpression)expression);
                        }
                        else
                        {
                            throw new NotSupportedException(expression.NodeType.ToString());
                        }
                        
                    case ExpressionType.Convert:
                        Type t = expression.NodeType.GetType();
                        var unary = (UnaryExpression)expression;
                        if (t.IsEnum)
                        {
                            if (unary.Operand.NodeType == ExpressionType.MemberAccess)
                            {
                                var men = (MemberExpression)(unary.Operand);
                                if (men.Expression != null && men.Expression.NodeType == ExpressionType.Parameter)
                                {
                                    return men.ToString();
                                }
                                else
                                {
                                    var objectMember = Expression.Convert(men, typeof(object));

                                    var getterLambda = Expression.Lambda<Func<object>>(objectMember);

                                    var getter = getterLambda.Compile();

                                    returnValue = getter();

                                    break;
                                }
                            }
                            else
                            {
                                return unary.Operand.ToString() == "null" ? "\"null\"" : unary.Operand.ToString();//unary.Operand.Value == null? unary.Operand.Value.ToString() :
                            }
                        }
                        var type = unary.Type;
                        string value = Evaluate(unary.Operand);

                        returnValue = Convert.ChangeType(value, type);

                        break;

                    case ExpressionType.Call:

                        var methodExpression = ((MethodCallExpression)expression);

                        string tempt = expression.ToString();//"Contains(\"a\")"
                        var argument = ((MethodCallExpression)expression).Arguments[0].ToString();
                        argument = argument.Substring(1, argument.Length - 2);

                        switch (methodExpression.Method.Name)
                        {
                            case "Contains":


                                if (methodExpression.Object != null)// && methodExpression.Object.ToString().IndexOf("i") == 0)
                                {
                                    return "(" + methodExpression.Object.ToString()
                                                        + " " + "like " + "\"%" + Expression.Lambda(((MethodCallExpression)expression).Arguments[0]).Compile().DynamicInvoke() + "%\"" + ")";
                                }
                                else
                                {
                                    var method = ((MethodCallExpression)expression);

                                    if (method.Arguments[1] != null)
                                    {
                                        argument = method.Arguments[1].ToString();
                                    }
                                    var objectMember = Expression.Convert(method.Arguments[0], typeof(object));
                                    if ((method.Arguments.Count == 1))
                                    {
                                        argument = method.Arguments[0].ToString();
                                        objectMember = Expression.Convert(method.Object, typeof(object));
                                    }
                                    var getterLambda = Expression.Lambda<Func<object>>(objectMember);

                                    var getter = getterLambda.Compile();

                                    returnValue = getter();

                                    var namelist = (IEnumerable)returnValue;

                                    var items = new List<object>();

                                    foreach (var item in namelist)
                                    {
                                        items.Add(item);
                                    }
                                    string contentarray = string.Join(",", items);
                                    return "(" + argument + " in" + " \"%" + contentarray + "%\"" + ")";
                                }


                            case "StartsWith":
                                return "(" + methodExpression.Object.ToString()
                                                                                   + " " + "like " + "\"" + argument + "%\"" + ")";


                            case "EndsWith":
                                return "(" + methodExpression.Object.ToString()
                                         + " " + "like " + "\"%" + argument + "\"" + ")";

                            default:
                                returnValue = Expression.Lambda(expression).Compile().DynamicInvoke();
                                break;

                        }


                        break;

                    default:
                        throw new NotSupportedException(expression.NodeType.ToString());

                }
            }

            return returnValue == null ? "\"null\"" : "\"" + returnValue.ToString().Replace("\"", "\\\"") + "\"";

        }



        static string Evaluate(BinaryExpression binaryExpression)
        {
            var command = "(";

            command += Evaluate(binaryExpression.Left) + " ";

            switch (binaryExpression.NodeType)
            {
                case ExpressionType.Equal:
                    command += "==";
                    break;
                case ExpressionType.NotEqual:
                    command += "!=";
                    break;
                case ExpressionType.LessThan:
                    command += "<";
                    break;
                case ExpressionType.GreaterThan:
                    command += ">";
                    break;
                case ExpressionType.LessThanOrEqual:
                    command += "<=";
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    command += ">=";
                    break;
                case ExpressionType.AndAlso:
                    command += "&&";
                    break;
                case ExpressionType.OrElse:
                    command += "||";
                    break;
                default:
                    throw new NotSupportedException(binaryExpression.NodeType.ToString());
            }


            command += " " + Evaluate(binaryExpression.Right) + ")";

            return command;


        }
        #endregion

    }
}
